iT邦幫忙

2024 iThome 鐵人賽

DAY 29
1
IT 管理

30天從版控到code review的實踐指南系列 第 29

Day 29. Code Review 模組化與重用性-範例篇-2

  • 分享至 

  • xImage
  •  

Day 1. 的規劃,今天主題是零星補充這個實踐系列的內容,那就繼續延續上一篇,來探討所謂測試覆蓋率高的程式碼,可能會長怎麼樣呢?

範例 3:高測試覆蓋率。

原始程式碼:


//根據圖幅編號拿到該圖幅編號第一筆資料的SHAPE
public string GetFrameList_SHAPE(string frame_id)
   {
       var SHAPE = "";
       try
       {
           var cnStr = _config.GetConnectionString("apiAuthConnection");
           
           // 可拆 1. 根據 frame_id 找到 shape 字串
           string sqlStr = @"SELECT SHAPE.ToString() AS Shape
                           FROM FRAME WHERE frame_id = @frame_id;";
           using (SqlConnection conn = new SqlConnection(cnStr))
           {
               SHAPE = conn.Query<string>(sqlStr, new { frame_id }).FirstOrDefault();
               
               // 可拆 2. shape 資料使用 WKTReader 轉成 polygon 物件
               var reader = new WKTReader();
               var geometry = reader.Read(SHAPE) as Polygon;

               if (geometry != null)
               {
		               // 可拆 3. 從 polygon 取得資料並組 JSON 陣列
                   var coordinates = geometry.ExteriorRing.Coordinates;
                   var jsonArray = new List<List<List<double>>>();
										
                   foreach (var coordinate in coordinates)
                        {
                            jsonArray.Add(new List<List<double>> { new List<double> { coordinate.X, coordinate.Y } });
                        }

                        var jsonString = JsonConvert.SerializeObject(jsonArray);
                        Console.WriteLine(jsonString);
                    }
                }
            }
            catch (Exception ex)
            {

                throw;
            }
            return SHAPE;
        }

重構後的程式碼:

將原本單一方法,拆分成好幾個小方法。


public class FrameShapeService
{
    private readonly IDbConnection _dbConnection;
    private readonly IWKTReader _wktReader;
    private readonly ILogger<FrameShapeService> _logger;

    public FrameShapeService(IDbConnection dbConnection, IWKTReader wktReader, ILogger<FrameShapeService> logger)
    {
        _dbConnection = dbConnection;
        _wktReader = wktReader;
        _logger = logger;
    }

    public async Task<string> GetFrameShapeAsync(string frameId)
    {
        try
        {
            var shape = await QueryFrameShapeAsync(frameId);
            var geometry = ParseWKT(shape);
            return SerializeGeometry(geometry);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting frame shape for frame ID {FrameId}", frameId);
            throw;
        }
    }

    private async Task<string> QueryFrameShapeAsync(string frameId)
    {
        const string sql = "SELECT SHAPE.ToString() AS Shape FROM FRAME WHERE frame_id = @frameId";
        return await _dbConnection.QueryFirstOrDefaultAsync<string>(sql, new { frameId });
    }

    private Polygon ParseWKT(string wkt)
    {
        return _wktReader.Read(wkt) as Polygon;
    }

    private string SerializeGeometry(Polygon geometry)
    {
        if (geometry == null) return null;

        var coordinates = geometry.ExteriorRing.Coordinates;
        var jsonArray = coordinates.Select(c => new List<List<double>> { new List<double> { c.X, c.Y } }).ToList();
        return JsonConvert.SerializeObject(jsonArray);
    }
}

單元測試

測試修改後的程式碼:


public class FrameShapeServiceTests
/*
測試輸入圖框編號,是否會正確回傳shap資料
期望成果 1. return 一個簡單的 POLYGON 字串。
	      2. WKTReader 會將輸入的 shape 字串解析為一個 Polygon 物件。
*/
{
    [Fact]
    public async Task GetFrameShapeAsync_ShouldReturnSerializedShape()
    {
        // Arrange
        var mockDbConnection = new Mock<IDbConnection>();
        mockDbConnection.Setup(db => db.QueryFirstOrDefaultAsync<string>(It.IsAny<string>(), It.IsAny<object>(), null, null, null))
            .ReturnsAsync("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))");

        var mockWktReader = new Mock<IWKTReader>();
        mockWktReader.Setup(r => r.Read(It.IsAny<string>()))
            .Returns(new Polygon(new LinearRing(new Coordinate[] {
                new Coordinate(0, 0),
                new Coordinate(0, 1),
                new Coordinate(1, 1),
                new Coordinate(1, 0),
                new Coordinate(0, 0)
            })));

        var mockLogger = new Mock<ILogger<FrameShapeService>>();

        var service = new FrameShapeService(mockDbConnection.Object, mockWktReader.Object, mockLogger.Object);

        // Act:執行 GetFrameShapeAsync 方法
        // 傳入一個虛擬的 frame_id,測試方法是否能正確執行並 return 序列化的 JSON 結果。
        var result = await service.GetFrameShapeAsync("testFrameId");

        // Assert:這部分檢查 return 的結果,確認它符合期望的 JSON 格式且不為 null。
        Assert.NotNull(result);
        var expectedJson = "[[[[0.0,0.0]]],[[[0.0,1.0]]],[[[1.0,1.0]]],[[[1.0,0.0]]],[[[0.0,0.0]]]]";
        Assert.Equal(expectedJson, result);
    }
}

Summary


今天分享了一個可測試性的程式碼案例,而單元測試通常在專門的測試專案中撰寫和執行,而不是直接在 Controller 中執行方法。這樣不僅可提高程式碼的可測試性,還能確保測試的獨立性和穩定性。此外,這些測試也可以在持續集成(CI)工具中自動執行,進一步提升開發流程的效率和可靠性。


上一篇
Day 28. Code Review 模組化與重用性-範例篇-1。
下一篇
Day 30. 如何從無到有,實踐 Code Review-系列文回顧與心得
系列文
30天從版控到code review的實踐指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言